// This is the main DLL file.

#include "stdafx.h"
 
#pragma managed
#include "Util.h"
#include "Database.h"
#include "Derived\ClassType.h"
using namespace System::IO;
using namespace System::Text;
using namespace System::Runtime::InteropServices; // for class Marshal
using namespace System::Diagnostics;

namespace ManagedAPI
{
	bool Database::Open( String^ file_name )
	{
		bool result = false;
		if (!mOpened)
		{
			const char* filename = Util::StringToChar(file_name);
			UdbStatus status = UdbStatus::Udb_statusOkay;
			
			status = (UdbStatus)udbDbOpen( filename ); 
			
			Util::FreeNativeString(filename);

			Debug::Assert(status == UdbStatus::Udb_statusOkay);

			if (status == UdbStatus::Udb_statusOkay)
			{
				result = true;
				mOpened = true;
			}
		}

		return result;
	}

	void Database::Close()
	{
		udbDbClose();
		Entity::Clear();
		EnumType::Clear();
		ClassType::Clear();
		FileEntity::Clear();
		MethodType::Clear();
		mOpened = false;
	}

	ManagedAPI::UdbLanguage Database::Language()
	{
		return (ManagedAPI::UdbLanguage)udbDbLanguage();
	}

	String^ Database::LanguageString()
	{
		UdbLanguage language = (ManagedAPI::UdbLanguage)udbDbLanguage();
		System::Text::StringBuilder^ sb = gcnew System::Text::StringBuilder();

		switch (language)
		{
			//case 0x00: sb->Append("NONE"); break;
			//case 0x3F: sb->Append("ALL"); break;
			//case 0x01: sb->Append("Ada"); break;
			case 0x02: sb->Append("C"); break;
			//case 0x04: sb->Append("CSharp"); break;
			//case 0x08: sb->Append("Fortran"); break;
			//case 0x10: sb->Append("Java"); break;
			//case 0x20: sb->Append("Jovial"); break;
			//case 0x40: sb->Append("Pascal"); break;
			default: sb->Append("NONE"); break;
		}
		return sb->ToString();
	}
	String^ Database::FileName()
	{
		String^ name = gcnew String(udbDbName());
		Debug::Assert(name != nullptr);
		return name;
	}
	array<Entity^>^ Database::GetEntities(understand_function function_pointer, bool free_memory)
	{
		int numEntities = 0;
		UdbEntity* pAllEnts = 0;
		function_pointer(&pAllEnts, &numEntities);

		array<Entity^>^ entityList = nullptr;
		if (pAllEnts != 0)
		{
			// do something with the entities
			entityList = gcnew array<Entity^>(numEntities);

			// Instantiate all Entities
			for (int i = 0; i < numEntities; i++)
			{
				UdbEntity ptr = pAllEnts[i];
				Debug::Assert(ptr != nullptr);
				if (ptr != nullptr)
				{
					Entity^ entptr = Entity::Create(ptr);
					entityList[i] = entptr;
				}
			}
			if (free_memory)
			{
				udbListEntityFree(pAllEnts);
			}
		}
		return entityList;
	}

	array<Entity^>^ Database::GetAllEntities()
	{
		// This passes a function pointer to reduce the amount of code
		// Return an allocated list of all entities. After a database update, the list is invalid and must be retrieved again.
		return GetEntities(udbListEntity, false);
	}
	array<Entity^>^ Database::GetFileEntities()
	{
		// This passes a function pointer to reduce the amount of code
		// Return a temporary list of all analyzed file entities (not allocated). 
		return GetEntities(udbListFile, false);
	}
	Entity^ Database::LookupFile(String^ file_name)
	{
		if (String::IsNullOrEmpty(file_name))
		{
			return nullptr;
		}

		const char* cfileName = Util::StringToChar(file_name);
		char* fileName = const_cast<char*>( cfileName );
		UdbEntity ent = udbLookupFile(fileName);
		Util::FreeNativeString(cfileName);
		cfileName = 0;
		fileName = 0;

		Entity^ result = nullptr;
		
		if (ent != nullptr)
		{
			result = Entity::Create(ent);
			Debug::Assert(result != nullptr);
		}

		return result;
	}
	Entity^ Database::LookupEntityByUniqueName(String^ unique_name)
	{
		if ( String::IsNullOrEmpty(unique_name))
		{
			return nullptr;
		}
		const char* cfileName = Util::StringToChar(unique_name);
		char* entity_name = const_cast<char*>( cfileName );
		UdbEntity entity = udbLookupEntityByUniquename(entity_name);
		Util::FreeNativeString(cfileName);
		cfileName = 0;
		entity_name = 0;

		Entity^ result = nullptr;
		if (entity != nullptr)
		{
			result = Entity::Create(entity);
			Debug::Assert(result != nullptr);
		}
		return result;
	}

	array<FileEntity^>^ Database::GetFileEntityArray()
	{
		int numEntities = 0;
		UdbEntity* pAllEnts = 0;
		udbListFile(&pAllEnts, &numEntities);

		array<FileEntity^>^ entityList = nullptr;
		if (pAllEnts != 0)
		{
			// do something with the entities
			entityList = gcnew array<FileEntity^>(numEntities);

			// Instantiate all Entities
			for (int i = 0; i < numEntities; i++)
			{
				UdbEntity ptr = pAllEnts[i];
				Debug::Assert(ptr != nullptr);
				if (ptr != nullptr)
				{
					FileEntity^ entptr = FileEntity::Create(ptr);
					entityList[i] = entptr;
				}
			}
		}
		return entityList;
	}
	array<ClassType^>^ Database::GetClassStruct(char* entitykind)
	{
		int numEntities = 0;
		UdbEntity* pAllEnts = 0;
		udbListEntity(&pAllEnts, &numEntities);
		UdbKindList kinds = udbKindParse(entitykind);
		UdbEntity* classList = 0;
		int numClasses = 0;
		udbListEntityFilter(pAllEnts, kinds, &classList, &numClasses);

		array<ClassType^>^ classTypeList = nullptr;
		if (pAllEnts != 0)
		{
			// do something with the entities
			classTypeList = gcnew array<ClassType^>(numClasses);

			// Instantiate all Entities
			for (int i = 0; i < numClasses; i++)
			{
				UdbEntity ptr = classList[i];
				Debug::Assert(ptr != nullptr);
				if (ptr != nullptr)
				{
					ClassType^ entptr = ClassType::Create(ptr);
					classTypeList[i] = entptr;
				}
			}
		}
		udbKindListFree(kinds);
		kinds = 0;
		udbListEntityFree(classList);
		classList = 0;
		return classTypeList;
	}
	array<ClassType^>^ Database::GetAllClassTypes()
	{
		return GetClassStruct("Class");
	}
	array<ClassType^>^ Database::GetAllStructTypes()
	{
		return GetClassStruct("Struct");
	}
	array<Entity^>^ Database::GetAllEnums()
	{
		int numEntities = 0;
		UdbEntity* pAllEnts = 0;
		udbListEntity(&pAllEnts, &numEntities);
		UdbKindList kinds = udbKindParse("Enum ~Unresolved ~Unnamed ~Member");
		UdbEntity* enumList = 0;
		int numEnums = 0;
		udbListEntityFilter(pAllEnts, kinds, &enumList, &numEnums);

		array<Entity^>^ enumTypeList = nullptr;
		if (enumList != 0)
		{
			// do something with the entities
			enumTypeList = gcnew array<Entity^>(numEnums);

			// Instantiate all Entities
			for (int i = 0; i < numEnums; i++)
			{
				UdbEntity ptr = enumList[i];
				Debug::Assert(ptr != nullptr);
				if (ptr != nullptr)
				{
					Entity^ entptr = Entity::Create(ptr);
					enumTypeList[i] = entptr;
				}
			}
		}
		udbKindListFree(kinds);
		kinds = 0;
		udbListEntityFree(enumList);
		enumList = 0;
		return enumTypeList;		
	}
	template<typename T> array<T^>^ GetTypes(char* kindString)
	{
		int numEntities = 0;
		UdbEntity* pAllEntities = 0;
		udbListEntity(&pAllEntities, &numEntities);
		UdbKindList kinds = udbKindParse(kindString);
		UdbEntity* pFilteredList = 0;
		int numFiltered = 0;
		udbListEntityFilter(pAllEntities, kinds, &pFilteredList, &numFiltered);

		array<T^>^ result = nullptr;
		if (pFilteredList != 0)
		{
			// do something with the entities
			result = gcnew array<T^>(numFiltered);

			// Instantiate all Entities
			for (int i = 0; i < numFiltered; i++)
			{
				UdbEntity ptr = pFilteredList[i];
				Debug::Assert(ptr != nullptr);
				if (ptr != nullptr)
				{
					T^ entptr = T::Create(ptr);
					result[i] = entptr;
				}
			}
		}
		udbKindListFree(kinds);
		kinds = 0;
		udbListEntityFree(pFilteredList);
		pFilteredList = 0;
		return result;		
	}
	array<MethodType^>^ Database::GetAllFunctions()
	{
		return GetTypes<MethodType>("function ~Member");
	}
	array<Entity^>^ Database::GetAllTypeDefs()
	{
		int numEntities = 0;
		UdbEntity* pAllEnts = 0;
		udbListEntity(&pAllEnts, &numEntities);
		// Understand C API call.
		UdbKindList kinds = udbKindParse("Typedef ~Unresolved ~Unnamed ~Member");
		UdbEntity* typedefList = 0;
		int numEnums = 0;
		// Understand C API call.
		udbListEntityFilter(pAllEnts, kinds, &typedefList, &numEnums);

		array<Entity^>^ typedefTypeList = nullptr;
		if (pAllEnts != 0)
		{
			// do something with the entities
			typedefTypeList = gcnew array<Entity^>(numEnums);

			// Instantiate all Entities
			for (int i = 0; i < numEnums; i++)
			{
				UdbEntity ptr = typedefList[i];
				Debug::Assert(ptr != nullptr);
				if (ptr != nullptr)
				{
					Entity^ entptr = Entity::Create(ptr);
					typedefTypeList[i] = entptr;
				}
			}
		}
		udbKindListFree(kinds);
		kinds = 0;
		udbListEntityFree(typedefList);
		typedefList = 0;
		return typedefTypeList;			
	}
	FileEntity^ Database::LookupFileEntity(String^ file_name)
	{
		if ( String::IsNullOrEmpty(file_name) )
		{
			return nullptr;
		}

		FileEntity^ result = nullptr;

		char* fileName = Util::StringToChar(file_name);
		// Understand C API call.
		UdbEntity ent = udbLookupFile(fileName);
		Util::FreeNativeString(fileName);
		fileName = 0;

		if (ent != nullptr)
		{
			result = FileEntity::Create(ent);
			Debug::Assert(result != nullptr);
		}

		return result;
	}
	Entity^ Database::LookupEntitybyReference(Type^ type, String^ filename, String^ entityName, int line, int column)
	{
		if ( String::IsNullOrEmpty( filename ))
			return nullptr;
		if ( String::IsNullOrEmpty( entityName ))
			return nullptr;
		
		char* fileName = Util::StringToChar(filename);
		// Understand C API call.
		UdbEntity fileEntity = udbLookupFile(fileName);
		Util::FreeNativeString(fileName);

		char* name = Util::StringToChar(entityName);
		int matched = 0;
		UdbEntity ent = udbLookupEntityByReference(fileEntity, name, line, column, &matched);
		Util::FreeNativeString(name);

		Entity^ result = nullptr;
		if (ent != nullptr)
		{
			result = FactoryCreate(type, ent);
			Debug::Assert(nullptr != result, "Entity is not valid");
		}
		return result;
	}
	Entity^ Database::LookupEntity(Type^ type, String^ entityName, String^ kindName)
	{
		if ( String::IsNullOrEmpty( entityName ))
		{
			return nullptr;
		}

		Entity^ result = nullptr;
		char* name = Util::StringToChar(entityName);
		char* kind = Util::StringToChar(kindName);
		int ALLOW_SHORT_NAMES = 1;
		UdbEntity* pEnts = 0;
		int numEnts = 0;
		// Understand C API call.
		udbLookupEntity(name, kind, ALLOW_SHORT_NAMES, &pEnts, &numEnts);

		Util::FreeNativeString(name);
		Util::FreeNativeString(kind);

		Debug::Assert(pEnts != nullptr, "nothing here");
		if (pEnts != 0)
		{
			for (int i = 0; i < numEnts ; i++)
			{
				UdbEntity ent = pEnts[i];
				Debug::Assert(nullptr != ent, "Entity is null");
				result = FactoryCreate(type, ent);
			}
		}
		return result;
	}
	Entity^ Database::FactoryCreate(Type^ type, UdbEntity ent)
	{
		Entity^ result = nullptr;

		     if (type == ClassType::typeid)
			result = ClassType::Create(ent);
		else if (type == EnumType::typeid)
			result = EnumType::Create(ent);
		else if (type == FileEntity::typeid)
			result = FileEntity::Create(ent);
		else if (type == MethodType::typeid)
			result = MethodType::Create(ent);
		else if (type == Entity::typeid)
			result = Entity::Create(ent);
		
		return result;
	}
}